摘要:本文记录一台裸金属服务器在"上游只给一个私有 /31 传输段 + 一段公网 /24 三层路由"的特殊网络结构下,如何完成 VirtFusion 被控节点(Hypervisor)的安装、存储规划、网络改造与首台虚拟机验收。文中包含全部输入命令与服务器回显,可直接照抄。
1. 环境与拓扑
1.1 硬件与软件
| 项目 | 配置 |
|---|---|
| CPU / 内存 | x86_64(支持 VT-x/AMD-V),约 4 TB RAM |
| 系统盘 | 1.92 TB NVMe(GPT:EFI 100 MB + 根分区 1.8 T) |
| 数据盘 | 15.36 TB NVMe(初始无分区表,4Kn 原生扇区) |
| 操作系统 | Debian 13 (Trixie),x86_64 |
| 面板 | VirtFusion 7.x(控制端 Control Server 已独立部署完成) |
| 网络 | 单网卡 enp1s0f0np0(万兆/25G 光口) |
1.2 上游网络结构
这是本次部署最特殊的地方,务必先理解清楚:
┌────────────────────────────┐
│ 上游路由器 │
│ 10.0.0.74/31 │
└─────────────┬──────────────┘
│ 私有 /31 传输段(点对点)
│
┌─────────────┴──────────────┐
│ 被控节点(本机) │
│ enp1s0f0np0 │
│ ├─ 10.0.0.75/31 │ ← 传输段地址
│ └─ 203.0.113.1/32 │ ← 宿主机公网地址
│ 默认路由: via 10.0.0.74 │
│ src 203.0.113.1 │
└─────────────┬──────────────┘
│ 三层路由:203.0.113.0/24 → 10.0.0.75
┌─────────────┴──────────────┐
│ 公网块 203.0.113.0/24 │
│ .1 = 宿主机 │
│ .2 ~ .253 = 客户虚拟机 │
└────────────────────────────┘原始 /etc/network/interfaces:
root@sg-hv-01:~# cat /etc/network/interfaces
source /etc/network/interfaces.d/*
auto lo
iface lo inet loopback
auto enp1s0f0np0
iface enp1s0f0np0 inet static
address 10.0.0.75/31
gateway 10.0.0.74
dns-nameservers 1.1.1.1
dns-nameservers 8.8.8.8
post-up ip addr replace 203.0.113.1/24 dev enp1s0f0np0
post-up ip route replace default via 10.0.0.74 dev enp1s0f0np0 src 203.0.113.1root@sg-hv-01:~# ip route
default via 10.0.0.74 dev enp1s0f0np0 src 203.0.113.1
10.0.0.74/31 dev enp1s0f0np0 proto kernel scope link src 10.0.0.75
203.0.113.0/24 dev enp1s0f0np0 proto kernel scope link src 203.0.113.1
root@sg-hv-01:~# ip route get 8.8.8.8
8.8.8.8 via 10.0.0.74 dev enp1s0f0np0 src 203.0.113.1 uid 0
cache三个关键结论:
- 上游把整段
203.0.113.0/24三层路由到传输段地址10.0.0.75,而不是把公网段放在二层同网段。 - 默认路由的源地址被显式钉在
203.0.113.1(否则出网会拿10.0.0.75当源地址)。 - 宿主机上把
/24直接配在物理网卡上(上游模板的写法)——这是后续所有网络问题的根源,第 6 节会详细说明。
2. 部署前检查
root@sg-hv-01:~# fdisk -l
Disk /dev/nvme0n1: 1.75 TiB, 1920383410176 bytes # 系统盘
Disk model: 1.92TB NVMe
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
Disklabel type: gpt
Device Start End Sectors Size Type
/dev/nvme0n1p1 2048 206847 204800 100M EFI System
/dev/nvme0n1p2 206848 3750746847 3750540000 1.8T Linux filesystem
Disk /dev/nvme1n1: 13.97 TiB, 15360000000000 bytes # 数据盘(无分区表)
Disk model: 15.36TB NVMe
Units: sectors of 1 * 4096 = 4096 bytes
Sector size (logical/physical): 4096 bytes / 4096 bytesroot@sg-hv-01:~# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p2 1.8T 814M 1.7T 1% /
/dev/nvme0n1p1 99M 9.0M 90M 10% /boot/efi
root@sg-hv-01:~# free -g
total used free shared buff/cache available
Mem: 4095 1 4088 0 6 4094检查硬件虚拟化(KVM 是硬性前提):
root@sg-hv-01:~# ls -l /dev/kvm
crw-rw---- 1 root kvm 10, 232 Oct 15 15:40 /dev/kvm
root@sg-hv-01:~# grep -oE 'vmx|svm' /proc/cpuinfo | sort -u
vmx注意:数据盘为 4Kn 原生扇区(logical/physical 均为 4096),格式化与分区对齐都需按 4K 处理。
3. 存储规划
3.1 设计目标
| 用途 | 方案 |
|---|---|
| 虚拟机磁盘 | 15.36 TB NVMe → ext4 → 挂载 /homedata |
| 交换空间 | 系统盘上 4 GiB swapfile |
| 虚拟机数据最终落点 | /homedata/vf-data/disk(通过面板指定) |
3.2 为什么 swap 用文件而不是分区
根分区 nvme0n1p2 已经从 206848 扇区占到磁盘末尾(3750746847),没有任何空闲空间。要划出独立的 swap 分区,必须:
- 用救援系统(IPMI 挂载 ISO)离线
e2fsck+resize2fs缩小已占满整盘的根文件系统; - 重建分区表并新增分区。
而内核与 KVM/QEMU 对待 swapfile 与 swap 分区没有实质区别,收益为零、风险极高。因此采用 swapfile。本机内存充裕,交换空间仅作为极端情况下的兜底、不作为日常内存扩展,因此设置为 4 GiB。
3.3 15.36 TB 数据盘:ext4 参数选择
root@sg-hv-01:~# wipefs -a /dev/nvme1n1
root@sg-hv-01:~# parted -s /dev/nvme1n1 mklabel gpt
root@sg-hv-01:~# parted -s /dev/nvme1n1 mkpart primary ext4 1MiB 100%
root@sg-hv-01:~# partprobe /dev/nvme1n1; udevadm settle
root@sg-hv-01:~# mkfs.ext4 -m 0 -T largefile -L homedata /dev/nvme1n1p1
mke2fs 1.47.2 (1-Jan-2025)
Discarding device blocks: done
Creating filesystem with 3749992960 4k blocks and 14648409 inodes
Filesystem UUID: 7c4e1d92-58ab-4f36-9c0d-2b7e5a1f8d64
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, ...
Allocating group tables: done
Writing inode tables: done
Creating journal (262144 blocks): done
Writing superblocks and filesystem accounting information: done两个关键的"最大化可用空间"参数:
| 参数 | 默认值的问题 | 本方案 |
|---|---|---|
-m 0 | ext4 默认给 root 保留 5% 块,15.36 TB 上即约 768 GB 白白锁死 | 数据盘无需保留,直接 -m 0 |
-T largefile | 默认 16 KiB/inode,15.36 TB 会生成约 9.4 亿 inode,inode 表占上百 GB,fsck 极慢 | 1 MiB/inode(约 1465 万 inode),虚拟机镜像全是大文件,完全够用 |
挂载与持久化:
root@sg-hv-01:~# mkdir -p /homedata
root@sg-hv-01:~# echo "UUID=$(blkid -s UUID -o value /dev/nvme1n1p1) /homedata ext4 defaults,noatime,nofail 0 2" >> /etc/fstab
root@sg-hv-01:~# systemctl daemon-reload && mount -a
root@sg-hv-01:~# df -hT /homedata
Filesystem Type Size Used Avail Use% Mounted on
/dev/nvme1n1p1 ext4 14T 2.1M 14T 1% /homedata为什么必须用 UUID:本机有两块 NVMe,实测硬重启后内核枚举顺序会变化,nvme0n1与nvme1n1会互换。若fstab里写/dev/nvme1n1p1,下次启动就会挂错盘甚至挂不上。nofail用于避免掉盘导致远程机器进入 emergency mode。
3.4 4 GiB swapfile
root@sg-hv-01:~# fallocate -l 4G /swapfile
root@sg-hv-01:~# chmod 600 /swapfile
root@sg-hv-01:~# mkswap -L vfswap /swapfile
Setting up swapspace version 1, size = 4 GiB (4294967296 bytes)
LABEL=vfswap, UUID=d94a2b63-7e15-4a8c-b0f2-3c6d98e14a77
root@sg-hv-01:~# swapon /swapfile
root@sg-hv-01:~# echo '/swapfile none swap sw 0 0' >> /etc/fstab
root@sg-hv-01:~# swapon --show
NAME TYPE SIZE USED PRIO
/swapfile file 4G 0B -2
root@sg-hv-01:~# free -h
total used free shared buff/cache available
Mem: 4.0Ti 1.3Gi 4.0Ti 1.0Mi 6.4Gi 4.0Ti
Swap: 4.0Gi 0B 4.0Gi踩坑记录:ext4 上
fallocate生成的是 unwritten extent,个别内核会拒绝swapon并报Invalid argument。若遇到,用dd原地实写覆盖(不会额外占用空间)后重新mkswap/swapon:dd if=/dev/zero of=/swapfile bs=4M count=1024 conv=notrunc status=progress mkswap -L vfswap /swapfile && swapon /swapfile
3.5 数据盘最终状态
root@sg-hv-01:~# lsblk -f
NAME FSTYPE FSVER LABEL UUID FSAVAIL FSUSE% MOUNTPOINTS
nvme0n1
└─nvme0n1p1 ext4 1.0 homedata 7c4e1d92-58ab-4f36-9c0d-2b7e5a1f8d64 13.9T 0% /homedata
nvme1n1
├─nvme1n1p1 vfat FAT32 4A21-9C0B 89.5M 9% /boot/efi
└─nvme1n1p2 ext4 1.0 b3f81a47-6d20-4c95-a7e3-91c4f0b28e5d 1.7T 1% /(注意此处 nvme0n1 已经是 15.36 TB 数据盘——与第 2 节的枚举顺序相反,正是"盘符不稳定"的实证。)
4. 安装被控节点(Hypervisor)
4.1 安装脚本安全审计
官方被控安装脚本:https://install.virtfusion.net/hypervisor-install.sh。在生产机上执行前,先下载并逐段审计其对系统的影响。
审计结论:
| 行为 | 是否发生 | 影响 |
|---|---|---|
修改 /etc/network/interfaces | 否 | 不动网络配置 |
| 创建网桥 / 修改 IP / 修改路由 | 否 | 不动网络配置 |
| 销毁 libvirt 默认网络 | 是(virsh net-destroy default) | 有益:避免 192.168.122.0/24 与既有网段冲突 |
覆盖 /etc/dnsmasq.conf 并停用 dnsmasq 服务 | 是 | 宿主机 DNS 来自 interfaces 的 dns-nameservers,无影响 |
| 启用 UFW | 是,但 default allow incoming/outgoing/routed | 不会造成失联 |
建立 /home/vf-data/{disk,os,server,tmp,lock,conf} | 是 | 虚拟机数据默认目录(见 7.3 的路径规划) |
| 监听端口 | 8892/tcp (https) | 控制端 ↔ 被控通信端口 |
4.2 安装
建议在 tmux / screen 中执行(脚本会安装 QEMU、libvirt、PHP 栈并生成 4096 位 DH 参数,耗时数分钟):
root@sg-hv-01:~# tmux new -s vf
root@sg-hv-01:~# ( set -euo pipefail; (command -v apt-get >/dev/null && apt-get update && apt-get install -y curl) || (command -v dnf >/dev/null && dnf -y install curl); curl -fsSL https://install.virtfusion.net/hypervisor-install.sh | bash )安装尾部输出:
[ OK ] Supervisor installation and configuration completed.
[INFO] Configuring firewall using UFW...
[ OK ] Firewall (UFW) configuration completed.
[INFO] Checking virtualization (KVM) support...
[ OK ] x86 virtualization flags detected and /dev/kvm present.
[ OK ] KVM appears available: /dev/kvm present.
[INFO] Log file: /var/log/2026-10-15-160759-hypervisor-install.log
[INFO] Rollback script retained at: /tmp/2026-10-15-160759-vf-hypervisor-rollback.sh
╔═══════════════════════════════════════════════════════════════════╗
║ ║
║ VirtFusion Hypervisor Installation Completed ║
║ ║
║ This hypervisor can now be added to the control server ║
║ ║
╚═══════════════════════════════════════════════════════════════════╝脚本会生成回滚脚本(/tmp/*-vf-hypervisor-rollback.sh),不要删除。VirtFusion 不提供卸载程序,装错只能重装系统。4.3 安装后自检
root@sg-hv-01:~# systemctl is-active vf-nginx vf-php8-fpm libvirtd supervisor
active
active
active
active
root@sg-hv-01:~# ss -lntp | grep 8892
LISTEN 0 511 0.0.0.0:8892 0.0.0.0:* users:(("nginx",pid=1800,fd=6),("nginx",pid=1799,fd=6),...)
LISTEN 0 511 [::]:8892 [::]:* users:(("nginx",pid=1800,fd=7),("nginx",pid=1799,fd=7),...)
root@sg-hv-01:~# virsh net-list --all
Name State Autostart Persistent
----------------------------------------------
default inactive no yes
root@sg-hv-01:~# virsh nwfilter-list
UUID Name
----------------------------------------------------------------
... allow-ipv6
... no-ipv6-spoofing
... vf-clean-v4
... vf-clean-v4v6在控制端面板添加被控后(地址 203.0.113.1,端口 8892),面板可读取到被控的内存与运行时间指标,说明通信链路已建立:
内存 1%
3315 MB of 4193280 MB Used
4189965 MB Free
运行时间 up 18:48, load average: 0.80, 0.51, 0.335. 网络方案选型
VirtFusion 支持以下被控网络类型:Bridged、Libvirt Routed、Libvirt Network、NAT v1、MacVTap、Open vSwitch Bridged(面板中"类型"下拉项)。
5.1 逐一排除
| 类型 | 在本环境是否可用 | 原因 |
|---|---|---|
MacVTap(默认) | 不可用于公网 IP 直通 | MacVTap 在 bridge 模式下宿主机无法与自己的虚拟机通信,客户机只能把网关指向上游 10.0.0.74(跨网段网关),Windows 及部分模板不支持;且会失去 IP 防伪与防火墙能力 |
Bridged(标准桥接) | 不适用 | 标准桥接要求公网 IP 与宿主机同二层;本环境是三层路由,桥接后上游无法 ARP 到客户机 IP |
NAT v1 | 可用(备选) | 客户机获取私网 IP、由宿主机 NAT 出网,通过面板做端口/域名映射。不需要改动物理网卡,但客户无法自由使用任意端口 |
Libvirt Routed | 本方案采用 | 与"私有 /31 + 公网 /24 三层路由"结构天然匹配:宿主机作为客户机的网关完成三层转发 |
面板中被控设置页明确提示:网络过滤(IP 防伪)仅在Libvirt Routed / Bridged / Libvirt Network / NAT v1下生效,MacVTap与Open vSwitch Bridged不可用——这也是排除 MacVTap 的重要依据。
5.2 判定上游是三层路由还是二层
root@sg-hv-01:~# apt install -y iputils-arping
root@sg-hv-01:~# ip neigh show 10.0.0.74
10.0.0.74 dev enp1s0f0np0 lladdr 00:11:22:33:44:55 REACHABLE
root@sg-hv-01:~# arping -c 3 -I enp1s0f0np0 203.0.113.200 # 一个未使用的地址
ARPING 203.0.113.200 from 203.0.113.1 enp1s0f0np0
Sent 3 probes (3 broadcast(s))
Received 0 response(s)无人应答 → 上游未对公网段做代理 ARP,符合三层路由特征。最终判定以首台虚拟机的实测为准(见第 8 节)。
5.3 目标架构
上游 10.0.0.74
│
enp1s0f0np0│ 10.0.0.75/31
│ 203.0.113.1/32
│
┌─────┴─────┐
│ 宿主机 │ 默认路由 via 10.0.0.74 src 203.0.113.1
│ ip_forward=1
└─────┬─────┘
│ br0 203.0.113.254/24 ← 客户机统一网关
┌─────────┼─────────┐
VM .2 VM .3 VM .4 ...6. 网络改造(高风险操作)
6.1 事故复盘:为什么不能在业务会话里直接 ip addr del
这次改造中发生过一次真实的失联事故,值得完整记录。
原始配置把 203.0.113.1/24 直接配在物理网卡上。为了让 libvirt 的路由型网络能使用该网段,必须先把物理网卡上的 /24 收窄为 /32,操作直觉上是:
ip addr del 203.0.113.1/24 dev enp1s0f0np0
ip addr add 203.0.113.1/32 dev enp1s0f0np0
ip route replace default via 10.0.0.74 dev enp1s0f0np0 src 203.0.113.1结果:第一条命令执行后 SSH 立即断开,后两条从未执行。
root@sg-hv-01:~# ip addr del 203.0.113.1/24 dev enp1s0f0np0
连接断开
连接主机(代理服务器)...
connection is closed by foreign host原因有两层:
- 删除
203.0.113.1意味着你当前 SSH 连接所使用的本地源地址消失,已建立的 socket 无法再选出合法的源地址,连接立即中断; - 三行命令是逐行执行的,第一行就把 shell 杀掉,后两行从未运行——机器最终处于"公网地址被删、新地址未加"的状态,从外部彻底不可达。
恢复方式:由于 /etc/network/interfaces 未被修改(改动仅在运行时),硬重启即自动恢复原始网络配置(post-up 会重新把 /24 与默认路由加回来)。
6.2 安全策略:三条铁律
基于上述事故,后续所有涉及"当前连接所依赖的地址/网卡"的变更,必须满足:
- 不在业务会话中执行:把整批变更交给独立执行单元(
systemd-run),SSH 断开后命令仍会跑完。 - 必须有定时自动回滚:变更前挂一个 10 分钟后的回滚任务,只有在变更验证通过后才取消它。若变更失败或自己再也连不上,机器会自动回到可用状态。
- 尽量留第二条命:临时增加一个同网段的备用管理地址,从该地址登录后再做手术。
前置条件:具备带外管理(IPMI / KVM over IP)或机房可代为硬重启。没有带外管理的单网卡机器,不应执行本文第 6 节的操作。
6.3 备用管理地址(可选,用于手术期间保命)
root@sg-hv-01:~# ip addr add 203.0.113.250/32 dev enp1s0f0np0
root@sg-hv-01:~# ip -br a show enp1s0f0np0
enp1s0f0np0 UP 10.0.0.75/31 203.0.113.1/24 203.0.113.250/32
# 从另一台机器验证后,再持久化到 interfaces(post-up 行)
root@sg-hv-01:~# sed -i '/203.0.113.1\/24/a\ post-up ip addr replace 203.0.113.250/32 dev enp1s0f0np0' /etc/network/interfaces
root@sg-hv-01:~# ifquery enp1s0f0np0 >/dev/null && echo "语法 OK"
语法 OK验证(在另一台机器上执行):
$ ssh -o ConnectTimeout=5 root@203.0.113.250 hostname
sg-hv-01备用地址只承担运维入口,不参与路由、不作为网关、面板也不识别它;建议在面板 IP 段中标记为"保留"。若不需要,删除interfaces中对应行并ip addr del即可。
6.4 变更脚本
回滚脚本 /root/net-rollback.sh:
#!/usr/bin/env bash
exec >>/root/net-rollback.log 2>&1; date; set -x
virsh net-destroy br0 2>/dev/null; virsh net-undefine br0 2>/dev/null
ip addr del 203.0.113.1/32 dev enp1s0f0np0 2>/dev/null
ip addr add 203.0.113.1/24 dev enp1s0f0np0 2>/dev/null
ip route replace default via 10.0.0.74 dev enp1s0f0np0 src 203.0.113.1
sed -i 's#203.0.113.1/32#203.0.113.1/24#' /etc/network/interfaces
ip -br a; ip route; echo ROLLED-BACKlibvirt 网络定义 /root/network.xml:
<network>
<name>br0</name>
<forward mode='route' dev="enp1s0f0np0"/>
<bridge name='br0' stp='on' delay='0'/>
<ip address='203.0.113.254' netmask='255.255.255.0'/>
</network>变更脚本 /root/net-switch.sh(自带分步自检,任一步失败立即回滚):
#!/usr/bin/env bash
exec >>/root/net-switch.log 2>&1; set -x
fail() { echo "FAILED: $*"; /root/net-rollback.sh; exit 1; }
ip addr del 203.0.113.1/24 dev enp1s0f0np0 || fail "del /24"
ip addr add 203.0.113.1/32 dev enp1s0f0np0 || fail "add /32"
ip addr add 203.0.113.250/32 dev enp1s0f0np0 2>/dev/null || true
ip route replace default via 10.0.0.74 dev enp1s0f0np0 src 203.0.113.1 || fail "default route"
virsh net-define /root/network.xml || fail "net-define"
virsh net-autostart br0
virsh net-start br0 || fail "net-start"
ip route | grep -q "203.0.113.0/24 dev br0" || fail "br0 route missing"
sed -i 's#203.0.113.1/24#203.0.113.1/32#' /etc/network/interfaces
ip -br a; ip route; virsh net-list --all
echo "SUCCESS"执行前的静态检查:
root@sg-hv-01:~# bash -n /root/net-switch.sh && bash -n /root/net-rollback.sh && echo "脚本语法 OK"
脚本语法 OK
root@sg-hv-01:~# virt-xml-validate /root/network.xml network && echo "XML OK"
/root/network.xml validates
XML OK6.5 执行
从备用管理地址(203.0.113.250)登录的新会话中执行:
root@sg-hv-01:~# systemd-run --on-active=10min --unit=net-rollback --collect /root/net-rollback.sh
Running timer as unit: net-rollback.timer
Will run service as unit: net-rollback.service
root@sg-hv-01:~# systemctl list-timers net-rollback.timer --no-pager
NEXT LEFT LAST PASSED UNIT ACTIVATES
Thu 2026-10-15 16:57:20 CST 9min - - net-rollback.timer net-rollback.service
1 timers listed.
root@sg-hv-01:~# systemd-run --unit=net-switch --collect /root/net-switch.sh
Running as unit: net-switch.service; invocation ID: fb2d440841244efd83baad8a67d2c6d9执行日志:
root@sg-hv-01:~# cat /root/net-switch.log
+ ip addr del 203.0.113.1/24 dev enp1s0f0np0
+ ip addr add 203.0.113.1/32 dev enp1s0f0np0
+ ip addr add 203.0.113.250/32 dev enp1s0f0np0
+ true
+ ip route replace default via 10.0.0.74 dev enp1s0f0np0 src 203.0.113.1
+ virsh net-define /root/network.xml
Network br0 defined from /root/network.xml
+ virsh net-autostart br0
Network br0 marked as autostarted
+ virsh net-start br0
Network br0 started
+ ip route
+ grep -q '203.0.113.0/24 dev br0'
+ sed -i s#203.0.113.1/24#203.0.113.1/32# /etc/network/interfaces
+ ip -br a
lo UNKNOWN 127.0.0.1/8 ::1/128
enp1s0f0np0 UP 10.0.0.75/31 203.0.113.250/32 203.0.113.1/32 fe80::1/64
br0 DOWN 203.0.113.254/24
+ ip route
default via 10.0.0.74 dev enp1s0f0np0 src 203.0.113.1
10.0.0.74/31 dev enp1s0f0np0 proto kernel scope link src 10.0.0.75
203.0.113.0/24 dev br0 proto kernel scope link src 203.0.113.254 linkdown
+ virsh net-list --all
Name State Autostart Persistent
----------------------------------------------
br0 active yes yes
default inactive no yes
+ echo SUCCESS
SUCCESS关于br0 DOWN与路由上的linkdown标记:Linux 网桥在没有成员端口时即处于 NO-CARRIER 状态,operstate显示down,其直连路由被标记linkdown。这不是故障。当第一台虚拟机的 tap 接口接入 br0 后,网桥转为 UP,标记随之消失。
6.6 验证与取消回滚
root@sg-hv-01:~# ip link show br0
5: br0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default qlen 1000
link/ether 52:54:00:9e:4b:17 brd ff:ff:ff:ff:ff:ff
root@sg-hv-01:~# sysctl -n net.ipv4.ip_forward
1
root@sg-hv-01:~# ping -c2 1.1.1.1
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=56 time=1.55 ms
64 bytes from 1.1.1.1: icmp_seq=2 ttl=56 time=1.56 ms
--- 1.1.1.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms尖括号中出现 UP 标志即表示网桥已管理性启用(NO-CARRIER 仅为无成员端口)。libvirt 在启动路由型网络时会自动开启 net.ipv4.ip_forward。
验证通过后立即取消回滚定时器,否则 10 分钟后变更会被撤销:
root@sg-hv-01:~# systemctl stop net-rollback.timer
root@sg-hv-01:~# systemctl list-units 'net-rollback*' --all
UNIT LOAD ACTIVE SUB DESCRIPTION
0 loaded units listed.7. VirtFusion 面板配置
7.1 被控网络
被控 → 网络 → 添加:
| 字段 | 值 | 说明 |
|---|---|---|
| 名称 | public-v4-routed | 面板内的标识 |
| 类型 | Libvirt 路由 | 即 Libvirt Routed |
| 接口名称 | br0 | 填网桥名,不是物理网卡名 |
| MTU | 留空 | 默认 1500 |
| IP 段分组 | -- 无 -- | 可选 |
面板只是引用该 libvirt 网络,不会重建它。配置完成后宿主机侧virsh net-list --all应仍为br0 active / autostart yes。
7.2 被控主接口与启用
- 被控 → 常规 → 打开 已启用(否则面板提示"虚拟机管理器未启用",无法创建服务器、不收集指标)。
- 若表单中存在主接口(Primary Interface)字段,填
enp1s0f0np0(该字段供 MacVTap 等模式使用)。
7.3 存储路径
被控默认的虚拟机存储路径为 /home/vf-data/disk,位于系统盘。将其指向 15.36 TB 数据盘:
root@sg-hv-01:~# mkdir -p /homedata/vf-data/disk
root@sg-hv-01:~# ls -ld /home/vf-data/disk /homedata/vf-data/disk
drwxr-xr-x 3 virtfusion virtfusion /home/vf-data/disk
drwxr-xr-x 2 root root /homedata/vf-data/disk
root@sg-hv-01:~# chown -R virtfusion:virtfusion /homedata/vf-data
root@sg-hv-01:~# ls -ld /homedata/vf-data/disk
drwxr-xr-x 2 virtfusion virtfusion /homedata/vf-data/disk被控 → 存储 → Edit Storage:
| 字段 | 值 |
|---|---|
| 类型 | Local (Default mountpoint) |
| Datastore | NVMe-4T |
| Path | /homedata/vf-data/disk |
| Max Capacity | ∞ 或 14000 |
保存后面板重新扫描,容量应显示约 14 TB。
常见疏漏:只执行
mkdir -p /homedata/vf-data而漏掉末级disk。面板 Path 指向不存在的目录会导致建机失败:# df -hT /homedata/vf-data/disk df: /homedata/vf-data/disk: No such file or directory关于 OS 模板目录:VirtFusion 的 OS 模板存放在
/home/vf-data/os,该目录仍在系统盘(与conf/同级)。不要把整层/home/vf-data绑定到数据盘——会覆盖/home/vf-data/conf/dnsmasq/,导致 libvirt 网络启动 dnsmasq 时找不到conf-dir。若模板占用过大,只迁移os这一层。
7.4 IP 段
被控 → IP 段 → 新建 IPv4 段:
| 字段 | 值 |
|---|---|
| 段 / 掩码 | 203.0.113.0/24 / 255.255.255.0 |
| 网关 | 203.0.113.254(必须等于 br0 上的地址,客户机才能 ARP 到) |
| DNS | 1.1.1.1、8.8.8.8 |
| 保留 | .0 .1 .250 .254 .255 |
保存后分配给该被控与 br0 网络,再逐个添加可分配的 IP。
保留地址的取舍:
| 地址 | 原因 |
|---|---|
.1 | 宿主机自身公网地址(SSH、8892 通信、默认路由源地址) |
.254 | br0 网关,所有客户机的默认网关 |
.250 | 备用管理地址(运维生命线,可选) |
.0 / .255 | 三层路由下技术可用,但客户机内部按 /24 计算时会被视为网络号/广播地址,部分系统与工具会异常(Windows 尤甚);VirtFusion 官方文档亦建议按惯例保留(compliance) |
一个 /24 的实际可售数量:256 - 5 = 251。
8. 首台虚拟机验收
分配 203.0.113.2,创建最小规格虚拟机(需先在 OS 模板 中准备好可用模板,Linux 模板须勾选 cloud-init,否则客户机不会自动应用分配到的 IP)。
宿主机侧:
root@sg-hv-01:~# virsh list --all
Id Name State
--------------------------------
1 vm-test-01 running
root@sg-hv-01:~# virsh domiflist vm-test-01
Interface Type Source Model MAC
-------------------------------------------------------------------
vnet0 bridge br0 virtio 52:54:00:c7:2f:8a
root@sg-hv-01:~# ip -br a show br0
br0 UP 203.0.113.254/24
root@sg-hv-01:~# ip route | grep 203.0.113
203.0.113.0/24 dev br0 proto kernel scope link src 203.0.113.254(网桥接入 tap 后转为 UP,此前的 linkdown 标记消失。)
客户机内(VNC 控制台):
# ip -br a
eth0 UP 203.0.113.2/24
# ip route
default via 203.0.113.254 dev eth0
203.0.113.0/24 dev eth0 proto kernel scope link src 203.0.113.2验收判据(三条全通即方案成立):
# 客户机 → 网关
# ping -c2 203.0.113.254
2 packets transmitted, 2 received, 0% packet loss
# 客户机 → 公网
# ping -c2 8.8.8.8
2 packets transmitted, 2 received, 0% packet loss
# 外部 → 客户机公网 IP
$ ping -c2 203.0.113.2
2 packets transmitted, 2 received, 0% packet loss9. 重启持久化验收
所有改动落地后必须重启复核一次:
root@sg-hv-01:~# reboot重启后:
root@sg-hv-01:~# ip -br a show enp1s0f0np0
enp1s0f0np0 UP 10.0.0.75/31 203.0.113.1/32 fe80::1/64
root@sg-hv-01:~# ip route
default via 10.0.0.74 dev enp1s0f0np0 src 203.0.113.1
10.0.0.74/31 dev enp1s0f0np0 proto kernel scope link src 10.0.0.75
203.0.113.0/24 dev br0 proto kernel scope link src 203.0.113.254
root@sg-hv-01:~# virsh net-list --all
Name State Autostart Persistent
----------------------------------------------
br0 active yes yes
default inactive no yes
root@sg-hv-01:~# sysctl -n net.ipv4.ip_forward
1
root@sg-hv-01:~# findmnt /homedata && df -hT /homedata
TARGET SOURCE FSTYPE OPTIONS
/homedata /dev/nvme0n1p1 ext4 rw,noatime
Filesystem Type Size Used Avail Use% Mounted on
/dev/nvme0n1p1 ext4 14T 2.1M 14T 1% /homedata
root@sg-hv-01:~# swapon --show
NAME TYPE SIZE USED PRIO
/swapfile file 4G 0B -2
root@sg-hv-01:~# ping -c2 1.1.1.1
2 packets transmitted, 2 received, 0% packet loss宿主机侧持久化涉及的三个位置:
| 内容 | 持久化方式 |
|---|---|
10.0.0.75/31、203.0.113.1/32、默认路由 | /etc/network/interfaces 的 address 与 post-up |
br0(含 203.0.113.254/24)与 ip_forward | libvirt 网络 autostart=yes,开机由 libvirtd 自动创建 |
/homedata、/swapfile | /etc/fstab(必须用 UUID,磁盘枚举顺序会变化) |
10. 故障排查手册
10.1 libvirt 报 Network is already in use by interface <nic>
error: Failed to start network br0
error: Network is already in use by interface enp1s0f0np0原因:物理网卡上仍存在与该 libvirt 网络完全相同的网段路由(例如残留的 203.0.113.1/24 直连路由)。libvirt 的 networkCheckRouteCollision() 会遍历 /proc/net/route,当某条路由的(网络地址 & 掩码)与网络定义完全一致时报错。
处理:把物理网卡上的 /24 收窄为 /32(/32 与 /24 掩码不同,不再触发冲突),操作务必遵循第 6.2 节的三条铁律。
10.2 客户虚拟机无法出网
# 1) 客户机的包是否到达网桥
root@sg-hv-01:~# tcpdump -ni br0 icmp -c 5
# 2) 是否被转发出去
root@sg-hv-01:~# tcpdump -ni enp1s0f0np0 icmp -c 5
# 3) 转发链是否被拦截
root@sg-hv-01:~# iptables -S FORWARD | head -20
# 4) 转发开关
root@sg-hv-01:~# sysctl -n net.ipv4.ip_forward判定与处置:
| 现象 | 结论 | 处置 |
|---|---|---|
包从 enp1s0f0np0 发出(源地址为客户机公网 IP)但无任何回包 | 上游未将公网段三层路由至本机,而是依赖二层 ARP | 由宿主机代客户机应答 ARP:sysctl -w net.ipv4.conf.enp1s0f0np0.proxy_arp=1并写入 /etc/sysctl.d/99-vf-proxyarp.conf 持久化 |
包根本没有出现在 br0 上 | 客户机未获得 IP/网关(通常是模板未启用 cloud-init) | 通过 VNC 进入客户机手动核对 ip a / ip route,并检查模板 cloud-init 配置 |
包在 br0 上出现但未出现在物理网卡 | 转发被拦截 | 检查 iptables -S FORWARD 与 UFW(官方安装脚本设置为 default allow routed) |
10.3 面板无法连接被控(8892 不通)
root@sg-hv-01:~# systemctl is-active vf-nginx
active
root@sg-hv-01:~# ss -lntp | grep 8892
LISTEN 0 511 0.0.0.0:8892 0.0.0.0:* users:(("nginx",pid=1800,fd=6),...)- 宿主机 UFW 默认放行入站,如自行调整过规则需放行
8892/tcp; - 上游若有防火墙策略,需允许控制端 IP 访问被控
8892/tcp; - 面板中填写的被控地址必须是可从控制端路由到达的地址(本例为
203.0.113.1)。
10.4 存储路径不存在导致建机失败
见 7.3 节的"常见疏漏",补建目录并 chown virtfusion:virtfusion 后在面板重新保存。
11. 经验清单
必须做
- 单网卡机器执行网络改造前,确认具备 IPMI/KVM 带外管理或机房可代为硬重启。
- 所有改变"当前连接所依赖地址"的操作,一律用
systemd-run独立单元执行,并挂定时自动回滚。 fstab一律使用UUID=;数据盘建议加nofail。- 虚拟机存储路径在面板中显式指向大容量数据盘,并确保目录已创建、属主正确。
- 改动后重启复核一遍。
禁止做
- 在业务 SSH 会话里直接
ip addr del掉当前连接使用的地址。 systemctl restart networking(会抖动接口,风险与上一条等价)。- 用
/dev/nvmeXn1之类的盘符写fstab或脚本(枚举顺序会变)。 - 把整层
/home/vf-data绑定到数据盘(会遮盖conf/dnsmasq/)。 - 保留地址(
.1/.254)分配给客户。
附录 A:宿主机侧文件清单
| 路径 | 用途 |
|---|---|
/root/network.xml | libvirt 路由型网络定义(br0) |
/root/net-switch.sh | 网络改造脚本(含分步自检) |
/root/net-rollback.sh | 回滚脚本 |
/root/net-switch.log、/root/net-rollback.log | 执行日志 |
/etc/network/interfaces.ok-<时间戳> | 改造前的网络配置备份 |
/tmp/*-vf-hypervisor-rollback.sh | VirtFusion 安装脚本生成的回滚脚本 |
/var/log/*-hypervisor-install.log | 被控安装日志 |
附录 B:参考资料
- VirtFusion 官方文档 — Hypervisor 安装:https://docs.virtfusion.com/installation/hypervisor/
- VirtFusion 官方文档 — Hetzner Routed Network(本文网络结构与该场景一致):https://docs.virtfusion.com/installation/provider-specific/hypervisor-hetzner-routed-subnet
- VirtFusion 官方文档 — Required Ports(被控
8892/tcp、VNC5900+):https://docs.virtfusion.com/reference/ports - VirtFusion 官方文档 — NAT(备选方案,无需改动物理网络):https://docs.virtfusion.com/guides/nat-setup
- libvirt 网络路由冲突检查逻辑:
src/network/bridge_driver_linux.c中的networkCheckRouteCollision()
暂无评论
还没有评论,来说点什么吧